/*
 * Copyright 2004-2014 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the Licence for the specific language governing permissions and limitations
 * under the Licence.
*/

package com.eviware.soapui.impl.wsdl.panels.loadtest;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.Scrollable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

import com.eviware.soapui.impl.wsdl.loadtest.ColorPalette;
import com.eviware.soapui.impl.wsdl.loadtest.WsdlLoadTest;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics.Statistic;
import com.eviware.soapui.impl.wsdl.loadtest.data.StatisticsHistory.StatisticsHistoryModel;
import com.jgoodies.forms.builder.ButtonBarBuilder;

/**
 * Graphical representation of testschedule statistics
 *
 * @author Ole.Matzura
 */

public class JStatisticsHistoryGraph extends JComponent implements Scrollable {
    private static final Color THREADCOUNT_COLOR = Color.GREEN.darker();
    private static final int SCROLL_AHEAD = 50;
    private static final Color TOTAL_COLOR = Color.BLACK;

    private final WsdlLoadTest loadTest;
    private final LoadTestStatistics statisticsModel;
    private StatisticsHistoryModel data;
    private JComponent legend;
    private InternalTableModelListener tableModelListener = new InternalTableModelListener();
    private long[] maxValues;
    private float[] scales;

    public JStatisticsHistoryGraph(WsdlLoadTest loadTest) {
        this.loadTest = loadTest;
        this.statisticsModel = loadTest.getStatisticsModel();
        this.data = statisticsModel.getHistory().getStatisticsValueHistory(Statistic.AVERAGE);

        setAutoscrolls(true);
        addMouseMotionListener(new InternalMouseMotionListener());

        data.addTableModelListener(tableModelListener);

        initMaxValues();
        initScales();

        setBackground(Color.WHITE);
        setOpaque(true);

        addComponentListener(new ComponentAdapter() {

            public void componentResized(ComponentEvent e) {
                initScales();
            }
        });
    }

    public long getResolution() {
        return statisticsModel.getHistory().getResolution();
    }

    public void setResolution(long resolution) {
        statisticsModel.getHistory().setResolution(resolution);
    }

    public TableModel getModel() {
        return data;
    }

    public void release() {
        data.removeTableModelListener(tableModelListener);
    }

    public void setStatistic(Statistic statistic) {
        if (data != null) {
            data.removeTableModelListener(tableModelListener);
            data.release();
        }

        data = statisticsModel.getHistory().getStatisticsValueHistory(statistic);

        initMaxValues();
        initScales();

        data.addTableModelListener(tableModelListener);

        getParent().invalidate();
        revalidate();
        repaint();
    }

    private void initMaxValues() {
        maxValues = new long[data.getColumnCount()];

        for (int c = 0; c < data.getRowCount(); c++) {
            for (int i = 0; i < data.getColumnCount(); i++) {
                long value = (Long) data.getValueAt(c, i);
                if (value > maxValues[i]) {
                    maxValues[i] = value;
                }
            }
        }
    }

    private void initScales() {
        scales = new float[maxValues.length];

        for (int c = 0; c < maxValues.length; c++) {
            recalcScale(c);
        }
    }

    private boolean recalcScale(int index) {
        float scale = (index == 0 || maxValues[index] == 0) ? 1 : (float) (getHeight())
                / (float) (maxValues[index] + 10);
        if (scale > 1) {
            scale = 1;
        }

        if (Float.compare(scale, scales[index]) == 0) {
            return false;
        }

        scales[index] = scale;
        return true;
    }

    public void paintComponent(Graphics g) {
        g.setColor(getBackground());

        Rectangle clip = g.getClipBounds();
        g.fillRect((int) clip.getX(), (int) clip.getY(), (int) clip.getWidth(), (int) clip.getHeight());

        double right = clip.getX() + clip.getWidth();
        int height = getHeight();

        for (int c = (int) clip.getX(); c < data.getRowCount() && c < right; c++) {
            for (int i = 0; i < data.getColumnCount(); i++) {
                if (i == 0) {
                    g.setColor(THREADCOUNT_COLOR);
                } else if (i == data.getColumnCount() - 1) {
                    g.setColor(TOTAL_COLOR);
                } else {
                    g.setColor(ColorPalette.getColor(loadTest.getTestCase().getTestStepAt(i - 1)));
                }

                int yOffset = (int) ((float) ((Long) data.getValueAt(c, i)) * scales[i]);

                if (clip.contains(c, height - yOffset - 1)) {
                    g.drawLine(c, height - yOffset - 1, c, height - yOffset - 1);
                }
            }
        }
    }

    public JComponent getLegend() {
        if (legend == null) {
            buildLegend();
        }

        return legend;
    }

    private void buildLegend() {
        ButtonBarBuilder builder = new ButtonBarBuilder();

        builder.addFixed(new JLabel("ThreadCount", createLegendIcon(THREADCOUNT_COLOR), JLabel.LEFT));
        builder.addUnrelatedGap();
        builder.addFixed(new JLabel("Total", createLegendIcon(TOTAL_COLOR), JLabel.LEFT));
        builder.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));

        legend = builder.getPanel();
    }

    private Icon createLegendIcon(Color color) {
        BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_3BYTE_BGR);
        Graphics g = image.getGraphics();
        g.setColor(color);
        g.fillRect(1, 1, 8, 8);
        g.setColor(Color.DARK_GRAY);
        g.drawRect(0, 0, 10, 10);
        return new ImageIcon(image);
    }

    public Dimension getPreferredScrollableViewportSize() {
        return getPreferredSize();
    }

    public Dimension getPreferredSize() {
        int height = getHeight();
        int width = data.getRowCount() + SCROLL_AHEAD;
        return new Dimension(width, height);
    }

    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return 1;
    }

    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return 10;
    }

    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    public boolean getScrollableTracksViewportHeight() {
        return true;
    }

    private final class InternalTableModelListener implements TableModelListener {
        public synchronized void tableChanged(TableModelEvent e) {
            boolean repaint = false;

            if (e.getType() == TableModelEvent.INSERT) {
                int firstRow = e.getFirstRow();
                int lastRow = e.getLastRow();
                int height = getHeight();

                for (int c = firstRow; c <= lastRow; c++) {
                    for (int i = 0; i < data.getColumnCount(); i++) {
                        long value = (Long) data.getValueAt(c, i);

                        if (value > maxValues[i]) {
                            maxValues[i] = value;
                            repaint = recalcScale(i);
                        }
                    }
                }

                if (!repaint) {
                    Rectangle rect = new Rectangle(firstRow, 0, (lastRow - firstRow) + 1, height);
                    repaint(rect);
                }

                Dimension size = getSize();
                Rectangle r = getVisibleRect();

                double x2 = r.getX() + r.getWidth();
                if (x2 >= data.getRowCount() && x2 < data.getRowCount() + SCROLL_AHEAD) {
                    scrollRectToVisible(new Rectangle(firstRow + SCROLL_AHEAD / 2, 0, (lastRow - firstRow) + 1, height));
                }

                if (!repaint && size.getWidth() < data.getRowCount() + SCROLL_AHEAD) {
                    revalidate();
                }
            } else if (e.getType() == TableModelEvent.UPDATE) {
                initMaxValues();
                initScales();

                repaint = true;
            }

            if (repaint) {
                getParent().invalidate();
                revalidate();
                repaint();
            }
        }
    }

    private class InternalMouseMotionListener implements MouseMotionListener {
        public void mouseDragged(MouseEvent e) {
            Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
            scrollRectToVisible(r);
        }

        public void mouseMoved(MouseEvent e) {
        }
    }
}
